package com.houger.ddd.datamanager.manager.aop;

import com.alibaba.fastjson.JSON;
import com.github.benmanes.caffeine.cache.Cache;
import com.houger.ddd.datamanager.manager.context.BaseBeanHolder;
import com.houger.ddd.datamanager.operator.InventoryOperator;
import com.houger.ddd.datamanager.utils.CacheType;
import com.houger.ddd.datamanager.utils.CacheUtils;
import com.houger.ddd.datamanager.utils.NullObject;
import com.houger.ddd.datamanager.utils.SpelUtils;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.concurrent.TimeUnit;

@Slf4j
@Aspect
@Component
@SuppressWarnings({"rawtypes","unchecked"})
public class DataCacheAspect {

    public static final String NULL_OBJECT = "NullObject";

    @Resource
    BaseBeanHolder baseBeanHolder;

    @Resource
    StringRedisTemplate stringRedisTemplate;

    @Pointcut("@annotation(com.houger.ddd.datamanager.manager.aop.DataCache)")
    public void pointCut() {

    }

    @Around("pointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {

        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        DataCache dataCache = method.getAnnotation(DataCache.class);

        Object returnObj = getReturnObj(joinPoint);

        if(returnObj != null){
            for (InventoryReference inventoryReference : dataCache.inventoryReference()) {
                updateInventoryReference(inventoryReference,returnObj);
            }
        }

        return returnObj;
    }

    private Object getReturnObj(ProceedingJoinPoint joinPoint) throws Throwable {
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        DataCache dataCache = method.getAnnotation(DataCache.class);

        boolean isLocal = false;
        boolean isRedis = false;

        for (CacheType cacheType : dataCache.type()) {
            if (cacheType == CacheType.LOCAL) {
                isLocal = true;
            }
            if (cacheType == CacheType.REDIS) {
                isRedis = true;
            }
        }

        long localExpire = dataCache.localExpire() > 0 ? dataCache.localExpire() : dataCache.expire();
        long redisExpire = dataCache.redisExpire() > 0 ? dataCache.redisExpire() : dataCache.expire();
        String cacheKey = SpelUtils.parseKey(dataCache.key(), joinPoint);
        String redisKey = CacheUtils.getRedisKey(dataCache.group(), cacheKey);

        if (isLocal) {
            Cache cacheManager = CacheUtils.getCacheManager(dataCache.group(), localExpire);
            Object obj = cacheManager.getIfPresent(cacheKey);
            if (obj != null) {
                if (obj instanceof NullObject) {
                    return null;
                } else {
                    return obj;
                }
            }
        }

        if (isRedis) {
            String objJson = this.stringRedisTemplate.opsForValue().get(redisKey);
            if (StringUtils.isNotEmpty(objJson)) {
                if (NULL_OBJECT.equals(objJson)) {
                    Cache cacheManager = CacheUtils.getCacheManager(dataCache.group(), localExpire);
                    cacheManager.put(cacheKey, new NullObject());
                    return null;
                } else {
                    Object obj = null;
                    if(Collection.class.isAssignableFrom(method.getReturnType())){
                        final Type actualTypeArgument = ((ParameterizedType) method.getGenericReturnType()).getActualTypeArguments()[0];
                        obj = JSON.parseArray(objJson, Class.forName(actualTypeArgument.getTypeName()));
                    } else {
                        obj = JSON.parseObject(objJson, method.getReturnType());
                    }
                    if (isLocal) {
                        Cache cacheManager = CacheUtils.getCacheManager(dataCache.group(), localExpire);
                        cacheManager.put(cacheKey, obj);
                    }
                    return obj;
                }
            }
        }

        Object returnObj = joinPoint.proceed();

        if (isLocal) {
            Cache cacheManager = CacheUtils.getCacheManager(dataCache.group(), localExpire);
            if (returnObj == null) {
                if (dataCache.cacheNull()) {
                    cacheManager.put(cacheKey, new NullObject());
                }
                return null;
            } else {
                cacheManager.put(cacheKey, returnObj);
            }
        }

        if (isRedis) {
            if (returnObj == null) {
                if (dataCache.cacheNull()) {
                    this.stringRedisTemplate.opsForValue().set(redisKey, NULL_OBJECT, redisExpire, TimeUnit.SECONDS);
                }
                return null;
            } else {
                this.stringRedisTemplate.opsForValue().set(redisKey, JSON.toJSONString(returnObj), redisExpire, TimeUnit.SECONDS);
            }
        }

        return returnObj;
    }


    private void updateInventoryReference(InventoryReference inventoryReference, Object returnObj){
        try {
            if(Collection.class.isAssignableFrom(returnObj.getClass())){
                for (Object obj : ((Collection) returnObj)) {
                    updateInventoryReferenceForObj(inventoryReference,obj);
                }
                return;
            }
            updateInventoryReferenceForObj(inventoryReference,returnObj);
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }

    private void updateInventoryReferenceForObj(InventoryReference inventoryReference, Object obj) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
        final InventoryOperator op = baseBeanHolder.getBean(inventoryReference.ref());
        BeanUtils.setProperty(obj,inventoryReference.field(),op.getUsed(op.getId(obj)));
    }

}
